<?php

/*
 * 上传类，支持多文件上传
 * @author php_angel <php_angel@126.com>
 * @date 2012-07-15
 */

class Upload extends Base{
	
	//上传路径
	protected $upload_path = '';
	
	//上传文件大小最大限制，单位KB
	protected $max_size = 0;
	
	protected $max_width = 0;
	
	protected $max_height = 0;
	
	//允许上传的文件类型(后缀名)，如array('jpg','gif')或者jpg|gif或jpg,gif
	protected $allowed_types = array();
	
	//上传文件名称
	protected $file_name = '';
	
	//是否覆盖
	protected $override = FALSE;
	
	//============================================================================================//
	
	//上传文件的临时文件地址（绝对地址）
	protected $file_temp = '';
	
	//上传文件名称（原始名称，覆盖重命名的原始名称）
	protected $orig_name = '';
	
	//上传文件的客服端名称
	protected $client_name = '';
	
	//上传文件大小
	protected $file_size = 0;
	
	//上传文件类型（MIME类型，如image/jpeg）
	protected $file_type = '';
	
	//上传文件后缀名，如jpg
	protected $file_extension = '';
	
	//图片文件宽度，单位px
	protected $image_width = 0;
	
	//图片文件高度，单位px
	protected $image_height = 0;
	
	//上传文件是否图片
	protected $is_image = FALSE;
	
	//待返回的结果集
	protected $datas = array();
	
	//配置项
	protected $defaults = array(
		'upload_path',
		'allowed_types',
		'max_size',
		'max_width',
		'max_height',
		'file_name',
		'override'
	);

	public function __construct($config = array())
	{
		$this->config($config);
	}
	
	public function init($config)
	{
		parent::config($config);
		
		if ($this->validate_upload_path() == FALSE)
		{
			exit($this->get_error());
		}
	}
	
	/*
	 * 上传文件，支持多文件上传
	 * @access public
	 * @param $field string 上传文件表单字段名称
	 * @return boolean
	 */
	public function do_upload($field = 'file')
	{
		$files = $this->get_files($field);
		if ( ! $files)
		{
			$this->set_error('上传文件找不到');
			return FALSE;
		}
		
		foreach ($files as $file)
		{
			if ( ! $this->upload_file($file))
			{
				return FALSE;
			}
		}
		
		if ( ! $this->move_file())
		{
			$this->set_error('移动文件错误');
			return FALSE;
		}
		
		return TRUE;
	}
	
	/*
	 * 单文件上传，除了用上面的方法上传，单文件这个方法也不行
	 * @access public
	 * @param $file array 文件，来自$_FILES
	 * @return boolean
	 */
	public function upload_file($file)
	{
		//判断上传文件错误
		if ( ! is_uploaded_file($file['tmp_name']))
		{
			//详细见PHP手册特点=》文件上传处理=》错误信息说明
			//http://cn2.php.net/manual/en/features.file-upload.errors.php
			switch ($file['error'])
			{
				case 1: //UPLOAD_ERR_INI_SIZE
					$this->set_error('上传的文件超过了系统限制的值');
					break;
				case 2: //UPLOAD_ERR_FORM_SIZE
					$this->set_error('上传文件的大小超过了表单中选项指定的值');
					break;
				case 3: //UPLOAD_ERR_PARTIAL
					$this->set_error('文件只有部分被上传');
					break;
				case 4: //UPLOAD_ERR_NO_FILE
					$this->set_error('没有文件被上传');
					break;
				case 6: //UPLOAD_ERR_NO_TMP_DIR
					$this->set_error('找不到临时文件夹');
					break;
				case 7: //UPLOAD_ERR_CANT_WRITE
					$this->set_error('文件写入失败');
				case 8: //UPLOAD_ERR_EXTENSION
					$this->set_error('文件扩展名被禁止');
					break;
				default :
					$this->set_error('未知错误');
					break;
			}

			return FALSE;
		}
		
		//对文件的一些属性赋值
		$this->file_temp = $file['tmp_name'];
		$this->file_type = $file['type'];
		$this->file_size = ceil($file['size']/1024);
		$this->file_extension = $this->get_extension($file['name']);
		$this->client_name = $file['name'];
		
		//设定上传文件名称
		if ( ! $this->file_name)
		{
			//没有提供名称，系统生成
			$this->file_name = substr(md5($this->file_temp.uniqid()), 0, 10);
		}
		else
		{
			$this->file_name = preg_replace('/'.$this->file_extension.'$/i', '', $this->file_name);
		}
		
		$this->file_name .= '.'.$this->file_extension;
		$this->orig_name = $this->file_name;
		
		//判断文件大小
		if ( ! $this->is_allowed_filesize())
		{
			$this->set_error('文件大小超过限制');
			return FALSE;
		}
		
		//判断文件类型
		if ( ! $this->is_allowed_filetype())
		{
			$this->set_error('不允许上传的文件类型');
			return FALSE;
		}
		
		//上传文件名称重命名，如果设定了重命名
		$this->override_file_name();
		
		//上传文件是否图片
		$this->validate_is_image();
		
		if ($this->is_image && ! $this->is_allowed_dimensions())
		{
			$this->set_error('图片尺寸超过限制');
			return FALSE;
		}
		
		//保存数据，待一起正式上传（移动临时文件到正式目录）
		$this->set_data();
		
		return TRUE;
	}
	
	/*
	 * 返回第一条上传文件数据
	 * @access public
	 * @return array
	 */
	public function get_data()
	{
		//为了始终返回第一条数据，reset下
		reset($this->datas);
		return current($this->datas);
	}
	
	/*
	 * 返回所有上传文件数据
	 * @access public
	 * @return array
	 */
	public function get_datas()
	{
		return $this->datas;
	}
	
	/*
	 * 设置保存目录
	 * @access protected
	 * @return void
	 */
	protected function set_upload_path($upload_path)
	{
		$this->upload_path = rtrim($upload_path, '/');
	}
	
	/*
	 * 设置上传文件名称
	 * @access protected
	 * @return void
	 */
	protected function set_file_name($file_name)
	{
		$this->file_name = trim($file_name);
		$this->clean_file_name();
	}
	
	/*
	 * 设置文件最大限制
	 * @access protected
	 * @return void
	 */
	protected function set_max_size($max_size)
	{
		$this->max_size = intval($max_size);
	}
	
	/*
	 * 设置上传图片高度限制
	 * @access protected
	 * @return void
	 */
	protected function set_max_width($max_width)
	{
		$this->max_width = intval($max_width);
	}
	
	/*
	 * 设置上传图片高度限制
	 * @access protected
	 * @return void
	 */
	protected function set_max_height($max_height)
	{
		$this->max_height = intval($max_height);
	}
	
	/*
	 * 设置允许的文件类型
	 * @access protected
	 * @return void
	 */
	protected function set_allowed_types($allowed_types)
	{
		if (is_string($allowed_types))
		{
			$s = strpos($allowed_types, '|') ? '|' : ',';
			$this->allowed_types = explode($s, $allowed_types);
		}
		elseif (is_array($allowed_types))
		{
			$this->allowed_types = $allowed_types;
		}
		
		$this->allowed_types = array_filter($this->allowed_types);
	}
	
	/*
	 * 设置是否覆盖
	 * @access protected
	 * @return void
	 */
	protected function set_override($override)
	{
		$this->override = (Boolean)$override;
	}
	
	/*
	 * 验证上传目录
	 * @access protected
	 * @return boolean
	 */
	protected function validate_upload_path()
	{
		if ( ! $this->upload_path)
		{
			$this->set_error('上传目录未设置');
			return FALSE;
		}

		$this->upload_path = str_replace('\\', '/', realpath($this->upload_path));
		
		if ( ! @is_dir($this->upload_path))
		{
			$this->set_error('上传目录不存在');
			return FALSE;
		}
		
		if ( ! is_writable($this->upload_path))
		{
			$this->set_error('上传目录无写入权限');
			return FALSE;
		}
		
		$this->upload_path = rtrim($this->upload_path, '/');
		
		return TRUE;
	}
	
	/*
	 * 检查是否图片，一般情况下的图片为jpg,gif,png
	 * @access protected
	 * @return void
	 */
	protected function validate_is_image()
	{
		$png_mimes  = array('image/x-png');
		$jpeg_mimes = array('image/jpg', 'image/jpe', 'image/jpeg', 'image/pjpeg');
		
		if (in_array($this->file_type, $png_mimes))
		{
			$this->file_type = 'image/png';
		}
		
		if (in_array($this->file_type, $jpeg_mimes))
		{
			$this->file_type = 'image/jpeg';
		}
		
		$img_mimes = array('image/gif', 'image/jpeg', 'image/png');
		
		if (in_array($this->file_type, $img_mimes))
		{
			if (function_exists('getimagesize'))
			{
				$d = getimagesize($this->file_temp);
				if ($d)
				{
					$this->image_width = $d[0];
					$this->image_height = $d[1];
					$this->is_image = TRUE;
				}
			}
		}
	}
	
	/*
	 * 从表单字段中获取文件
	 * @access protected
	 * @param $field string 上传文件表单字段名称
	 * @return boolean|array
	 */
	protected function get_files($field = 'file')
	{
		if ( ! isset($_FILES[$field]) || ! $_FILES[$field])
		{
			return FALSE;
		}
		
		$files = array();
		
		if (is_array($_FILES[$field]['name']))
		{
			for ($i=0; $i<count($_FILES[$field]['name']); $i++)
			{
				if ($_FILES[$field]['name'][$i])
				{
					$file = array();
					foreach (array_keys($_FILES[$field]) as $key)
					{
						$file[$key] = $_FILES[$field][$key][$i];
					}
					$files[] = $file;
				}
			}
		}
		else
		{
			if ( ! $_FILES[$field]['name'])
			{
				return FALSE;
			}
			$files[] = $_FILES[$field];
		}
		
		return $files ? $files : FALSE;
	}
	
	/*
	 * 覆盖重命名
	 * @access protected
	 * @param $i integer 递归时用到的名称索引
	 * @param $file_name string 名称
	 * @return void
	 */
	protected function override_file_name($i = 1, $file_name = NULL)
	{
		if ( ! $file_name)
		{
			$file_name = $this->file_name;
		}
		
		if ( ! $this->override && file_exists($this->upload_path.'/'.$file_name))
		{
			$file_name = preg_replace('/^(.*)\.'.$this->file_extension.'$/', '\\1_'.$i.'.'.$this->file_extension, $this->file_name);
			$i++;
			$this->override_file_name($i, $file_name);
		}
		else
		{
			$this->file_name = $file_name;
		}
	}
	
	/*
	 * 清除名称中的非法字符（用在自定义名称时）
	 * @access protected
	 * @return void
	 */
	protected function clean_file_name()
	{
		$bad = array(
						"<!--",
						"-->",
						"'",
						"<",
						">",
						'"',
						'&',
						'$',
						'=',
						';',
						'?',
						'/',
						"%20",
						"%22",
						"%3c",		// <
						"%253c",	// <
						"%3e",		// >
						"%0e",		// >
						"%28",		// (
						"%29",		// )
						"%2528",	// (
						"%26",		// &
						"%24",		// $
						"%3f",		// ?
						"%3b",		// ;
						"%3d"		// =
					);

		$this->file_name = stripslashes(str_replace($bad, '', $this->file_name));
	}
	
	/*
	 * 是否允许的文件大小
	 * @access protected
	 * @return boolean
	 */
	protected function is_allowed_filesize()
	{	
		if ($this->max_size!=0 && $this->file_size>$this->max_size)
		{
			return FALSE;
		}
		
		return TRUE;
	}
	
	/*
	 * 是否允许的文件类型
	 * @access protected
	 * @return boolean
	 */
	protected function is_allowed_filetype()
	{
		if ( ! $this->allowed_types)
		{
			return TRUE;
		}
		
		return in_array($this->file_extension, $this->allowed_types);
	}
	
	/*
	 * 是否允许的尺寸，图片有效
	 * @access protected
	 * @return boolean
	 */
	protected function is_allowed_dimensions()
	{
		//不是图片，直接返回true
		if ( ! $this->is_image)
		{
			return TRUE;
		}
		
		if ($this->max_width && $this->image_width>$this->max_width)
		{
			return FALSE;
		}
		
		if ($this->max_height && $this->image_height>$this->max_height)
		{
			return FALSE;
		}
		
		return TRUE;
	}
	
	/*
	 * 返回后缀名
	 * @access protected
	 * @param $filename string 文件名称
	 * @return string
	 */
	protected function get_extension($filename)
	{
		$a = explode('.', $filename);
		return end($a);
	}
	
	/*
	 * 移动文件，一切准备无误后统一上传
	 * @access protected
	 * @return boolean
	 */
	protected function move_file()
	{
		if ($this->datas)
		{
			foreach ($this->datas as $data)
			{
				//移动文件
				//copy的效率要远高于move_uploaded_file
				if ( ! @copy($data['file_temp'], $data['upload_path'].'/'.$data['file_name']))
				{
					if ( ! @move_uploaded_file($data['file_temp'], $data['upload_path'].'/'.$data['file_name']))
					{
						return FALSE;
					}
				}
			}
		}
		return TRUE;
	}
	
	/*
	 * 删除已经上传的文件（一般用在上传部分失败的时候）
	 * @access public
	 * @return void
	 */
	public function unlink_file()
	{
		if ($this->datas)
		{
			foreach ($this->datas as $data)
			{
				if (file_exists($data['upload_path'].'/'.$data['file_name']))
				{
					@unlink($data['upload_path'].'/'.$data['file_name']);
				}
			}
		}
		$this->datas = array();
	}
	
	/*
	 * 保存数据
	 * @access protected
	 * @return void
	 */
	protected function set_data()
	{
		$this->datas[] = array(
			'upload_path' => $this->upload_path,
			'client_name' => $this->client_name,
			'orig_name' => $this->orig_name,
			'file_temp' => $this->file_temp,
			'file_name' => $this->file_name,
			'file_size' => $this->file_size,
			'file_type' => $this->file_type,
			'file_extension' => $this->file_extension,
			'is_image' => $this->is_image,
			'image_width' => $this->image_width,
			'image_height' => $this->image_height,
		);
		
		$this->client_name = '';
		$this->orig_name = '';
		$this->file_temp = '';
		$this->file_size = 0;
		$this->file_type = '';
		$this->file_extension = '';
		$this->is_image = FALSE;
		$this->image_width = 0;
		$this->image_height = 0;
		//可以覆盖的话，多文件上传的时候设置file_name为空，让余下的系统命名
		if ($this->override)
		{
			$this->file_name = '';
		}
	}

}